﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2013-07-27 13:25
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BassDrop.Core.Common.Data;
using System.IO;
using BassDrop.Core.Common.Localization;
using BassDrop.Core.Common.Data.IO.Portable;
using BassDrop.Core.Common.Cloud.Requests;
using BassDrop.Core.Common.Services;
using BassDrop.Core.Common.Cloud.Requests.Assets;
using System.ComponentModel;

namespace BassDrop.Core.Common.Cloud
{
	/// <summary>
	/// An object that is persisted in some kind of file that can be stored in the cloud
	/// </summary>
	/// <typeparam name="T">the type of the represented object</typeparam>
	public abstract class RemoteFileObjectBase<T> : FileObjectBase<T>, ISynchronizable, IDisposable
	{
		/// <summary>
		/// The synch request scheduled for this item
		/// </summary>
		private CloudRequestBase m_synchRequest = null;

		/// <summary>
		/// Gets the lock used in this object
		/// </summary>
		protected abstract object Lock
		{
			get;
		}

		/// <summary>
		/// The path of the designated file of this object in the cloud
		/// </summary>
		public string RemotePath
		{
			get;
			private set;
		}

		/// <summary>
		/// Can the file be synchronized currently, is it actually remote
		/// or just a local file stored as a remote file object
		/// </summary>
		public bool Synchronizable
		{
			get
			{
				lock ( Lock )
				{
					return RemotePath != null && CloudConnection != null && CloudConnection.IsAvailable;
				}
			}
		}

		private RemoteFileObjectState m_state;

		/// <summary>
		/// The current state of readyness of this object or the current operation being performed on it
		/// </summary>
		public RemoteFileObjectState State
		{
			get
			{
				return m_state;
			}

			private set
			{
				if ( m_state != value )
				{
					m_state = value;
					OnPropertyChanged( new PropertyChangedEventArgs( "State" ) );
				}
			}
		}

		/// <summary>
		/// Object providing the connection to the cloud storing this file
		/// </summary>
		public CloudConnectionBase CloudConnection
		{
			get;
			private set;
		}


		/// <summary>
		/// Call this from subclasses to set the path and remote path to the designated file of the object
		/// </summary>
		/// <param name="representedObject">the initial state of the represented object (or null)</param>
		/// <param name="filePath">the path of the designated file of this object in the system</param>
		/// <param name="remotePath">the path of the designated file of this object in the cloud. If null, the file will be local only.</param>
		/// <param name="cloudConnection">the object providing the connection to the cloud storing this file. If null, the file will be local only.</param>
		protected RemoteFileObjectBase( ref T representedObject, Uri filePath, string remotePath, CloudConnectionBase cloudConnection )
			: base( ref representedObject, filePath )
		{
			State = RemoteFileObjectState.Initializing;

			RemotePath = remotePath;
			CloudConnection = cloudConnection;

			ScheduleInit();
		}

		/// <summary>
		/// Schedules the asynchronous initialization of this file object
		/// </summary>
		private void ScheduleInit()
		{
			lock ( Lock )
			{
				// TODO: az egész legyen aszinkron
				try
				{
					if ( RepresentedObject == null )
					{
						if ( LocalFileExists() )
						{
							Load();
						}
						else
						{
							CreateNew();
						}
					}
					else
					{
						Save();
					}


					if ( Synchronizable )
					{
						ScheduleSynchronize();
					}
					else
					{
						State = RemoteFileObjectState.Local;
					}

					Register();
				}
				catch ( Exception e )
				{
					State = RemoteFileObjectState.Error;
					Error = e;
				}
			}
		}

		/// <summary>
		/// Registers this object in the cloud manager. Call Dispose() to unregister.
		/// </summary>
		private void Register()
		{
			lock ( Lock )
			{
				if ( RemotePath != null && CloudConnection != null )
				{
					CloudManager.Instance.CloudObjects.Add( this );
				}
			}
		}

		/// <summary>
		/// Schedules the (asynchronous) synchronization of this object with the one stored in the cloud
		/// </summary>
		public void ScheduleSynchronize()
		{
			lock ( Lock )
			{
				if ( !Synchronizable )
				{
					throw new InvalidOperationException( "The item is not synchronizable now." );
				}

				if ( m_synchRequest != null )
				{
					return;
				}

				string tempPath = ServiceCenter.FileHandler.GetTempFileName();
				ulong lockId = RequestFactory.GenerateLockId();

				m_synchRequest = RequestFactory.MakeCompoundRequest(
						RequestFactory.MakeLockRequest( RemotePath, lockId, RequestFactory.LockTimeout, true ), // TODO: lock idő
						RequestFactory.MakeExistsRequest( RemotePath, async ( existsRemote, conn ) =>
							{
								bool needPut = !existsRemote;

								if ( existsRemote )
								{
									try
									{
										State = RemoteFileObjectState.Fetching;

										string oldHash = null;

										if ( ServiceCenter.FileHandler.Exists( FilePath.LocalPath ) )
										{
											oldHash = ServiceCenter.FileHandler.GetMD5Hash( FilePath.LocalPath );
										}

										await conn.Get( RemotePath, new Uri( tempPath ) );
										SynchFrom( new Uri( tempPath ) );

										Save();

										needPut = ServiceCenter.FileHandler.GetMD5Hash( FilePath.LocalPath ) != oldHash;
									}
									finally
									{
										ServiceCenter.FileHandler.Delete( tempPath );
									}
								}
								else
								{
									Save();
								}

								if ( needPut )
								{
									State = RemoteFileObjectState.Pushing;

									await conn.Put( RemotePath, FilePath );
								}
							} ),
						RequestFactory.MakeUnlockRequest( RemotePath, lockId )
					);

				m_synchRequest.RequestCanceled += synchRequest_RequestCanceled;
				m_synchRequest.RequestExecutionStarted += synchRequest_RequestExecutionStarted;
				m_synchRequest.RequestErrored += synchRequest_RequestErrored;
				m_synchRequest.RequestSucceeded += synchRequest_RequestSucceeded;

				CloudManager.Instance.Schedule( m_synchRequest, CloudConnection );
			}
		}

		/// <summary>
		/// Set the contents of this object to the contents of a new, empty one.
		/// Called when neither a remote, nor a local file exists.
		/// </summary>
		protected abstract void CreateNew();

		/// <summary>
		/// Synchronizes the contents of this object from the file on the passed uri
		/// </summary>
		/// <param name="uri"></param>
		protected abstract void SynchFrom( Uri uri );

		private void synchRequest_RequestExecutionStarted( object sender, Requests.Assets.CloudRequestStartedEventArgs e )
		{
			lock ( Lock )
			{
				m_synchRequest.RequestCanceled -= synchRequest_RequestCanceled;
				m_synchRequest.RequestExecutionStarted -= synchRequest_RequestExecutionStarted;
				m_synchRequest.RequestErrored -= synchRequest_RequestErrored;
				m_synchRequest.RequestSucceeded -= synchRequest_RequestSucceeded;

				m_synchRequest = null;
			}
		}

		private void synchRequest_RequestCanceled( object sender, Requests.Assets.CloudRequestCanceledEventArgs e )
		{
			lock ( Lock )
			{
				m_synchRequest.RequestCanceled -= synchRequest_RequestCanceled;
				m_synchRequest.RequestExecutionStarted -= synchRequest_RequestExecutionStarted;
				m_synchRequest.RequestErrored -= synchRequest_RequestErrored;
				m_synchRequest.RequestSucceeded -= synchRequest_RequestSucceeded;

				m_synchRequest = null;
			}
		}

		private void synchRequest_RequestSucceeded( object sender, CloudRequestSucceededEventArgs e )
		{
			lock ( Lock )
			{
				State = RemoteFileObjectState.Remote;
			}
		}

		private void synchRequest_RequestErrored( object sender, CloudRequestErroredEventArgs e )
		{
			lock ( Lock )
			{
				State = RemoteFileObjectState.Error;
				Error = e.Cause;
			}
		}

		#region IDisposable Members

		/// <summary>
		/// Removes this file object from where it has registered itself and cancels its async processes
		/// </summary>
		public void Dispose()
		{
			lock ( Lock )
			{
				if ( m_synchRequest != null )
				{
					m_synchRequest.Cancel( true );
				}

				if ( CloudManager.Instance != null )
				{
					CloudManager.Instance.CloudObjects.Remove( this );
				}
			}
		}

		#endregion
	}
}
